/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.util;

import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A factory for loading URL protocol handlers. This factory is necessary to make
 * Mule work in cases where the standard approach using system properties does not
 * work, e.g. in application servers or with maven's surefire tests.
 * <p>
 * Client classes can register a subclass of {@link URLStreamHandler} for a given
 * protocol. This implementation first checks its registered handlers before
 * resorting to the default mechanism.
 * <p>
 * @see java.net.URL#URL(String, String, int, String)
 */
public class MuleUrlStreamHandlerFactory extends Object implements URLStreamHandlerFactory
{
    
    private static final String HANDLER_PKGS_SYSTEM_PROPERTY = "java.protocol.handler.pkgs";
    private static final Log log = LogFactory.getLog(MuleUrlStreamHandlerFactory.class);
    
    private static Map registry = Collections.synchronizedMap(new HashMap());

    /**
     * Install an instance of this class as UrlStreamHandlerFactory. This may be done exactly
     * once as {@link URL} will throw an {@link Error} on subsequent invocations. 
     * <p>
     * This method takes care that multiple invocations are possible, but the 
     * UrlStreamHandlerFactory is installed only once.
     */
    public static synchronized void installUrlStreamHandlerFactory()
    {
        /*
         * When running under surefire, this class will be loaded by different class loaders and
         * will be running in multiple "main" thread objects. Thus, there is no way for this class
         * to register a globally available variable to store the info whether our custom 
         * UrlStreamHandlerFactory was already registered.
         * 
         * The only way to accomplish this is to catch the Error that is thrown by URL when
         * trying to re-register the custom UrlStreamHandlerFactory.
         */
        try
        {
            URL.setURLStreamHandlerFactory(new MuleUrlStreamHandlerFactory());
        }
        catch (Error err)
        {
            if (log.isDebugEnabled())
            {
                log.debug("Custom MuleUrlStreamHandlerFactory already registered", err);
            }
        }
    }
    
    public static void registerHandler(String protocol, URLStreamHandler handler)
    {
        registry.put(protocol, handler);
    }

    public URLStreamHandler createURLStreamHandler(String protocol)
    {
        URLStreamHandler handler = (URLStreamHandler) registry.get(protocol);
        if (handler == null)
        {
            handler = this.defaultHandlerCreateStrategy(protocol);
        }
        return handler;
    }

    private URLStreamHandler defaultHandlerCreateStrategy(String protocol)
    {
        String packagePrefixList = System.getProperty(HANDLER_PKGS_SYSTEM_PROPERTY, "");

        if (packagePrefixList.endsWith("|") == false)
        {
            packagePrefixList += "|sun.net.www.protocol";
        }

        StringTokenizer tokenizer = new StringTokenizer(packagePrefixList, "|");

        URLStreamHandler handler = null;
        while (handler == null && tokenizer.hasMoreTokens())
        {
            String packagePrefix = tokenizer.nextToken().trim();
            String className = packagePrefix + "." + protocol + ".Handler";
            try
            {
                handler = (URLStreamHandler) ClassUtils.instanciateClass(className);
            }
            catch (Exception ex)
            {
                // not much we can do here
            }
        }
         
        return handler;
    }
}
